import { ref, reactive } from "vue";
import { ElMessage } from "element-plus";
import * as turf from "@turf/turf";

import { Store } from "../store";
import { registerHotKey } from "./hotkeys";
import { setItem, getItem } from "./storage";
import { gcj02_To_gps84 } from "./convert";

export const BOUNDARY = "boundary";
export const POLYGON = "polygon";
export const POLYLINE = "polyline";
export const MARKER = "marker";

export const BUILDING = "building";
export const WATER = "WATER";
export const GRASS = "GRASS";
export const ROAD = "ROAD";

export const STATUS_NEW_POLYLON = "newPolylon";
export const STATUS_EDIT_POLYLON = "editPolylon";
export const STATUS_NEW_POLYLINE = "newPolyline";
export const STATUS_EDIT_POLYLINE = "editPolyline";
export const STATUS_EDIT_BOUNDARY = "editBoundary";

export const CENTER = [116.387175, 39.876405];
export const ZOOM = 16;

const BOUNDARY_BACKGROUND = "boundaryBackground";
const BOUNDARY_Z_INDEX = 20;
const BOUNDARY_IMAGE_Z_INDEX = 10;
const OVERLAY_Z_INDEX = 100;

const POLYLON_STROKE = "#4fbbbb";
const POLYLINE_STROKE = "#7641aa";
const BOUNDARY_STROKE = "#949492";
const SELECTED_STROKE = "#d80242";


let ID = 1;
const uuid = () => ++ID;
const CACHE_KEY = "FineGeoJSON";

export class Editor {

  constructor() {
    // 状态信息
    this.status = reactive({
      type: "",
      editing: false,
      searching: false,
    });
    this.markers = reactive([]);
    // 多边形集合
    this.polygons = reactive([]);
    // 多段线
    this.polylines = reactive([]);
    // 边界背景图层
    this.boundaryImageLayer = null;
    // 边界信息
    this.boundary = reactive({
      target: null,
      background: "",
    });
    // 覆盖物缓存
    this.overlayMap = {};
    // 选中的覆盖物信息
    this.selectedOverlay = reactive({
      info: null,
      type: "",
    });

    // 时间回调
    this.onBoundaryClick = (e) => this.onOverlayClick(e, BOUNDARY);
    this.onPolygonClick = (e) => this.onOverlayClick(e, POLYGON);
    this.onPolylineClick = (e) => this.onOverlayClick(e, POLYLINE);
    this.onMarkerClick = (e) => this.onOverlayClick(e, MARKER);
    this.updateMarkerPosition = this.updateMarkerPosition.bind(this);
    this.saveDraft = this._saveDraft.bind(this);
    // 
    this.setupHotKeys();
    // 每2分钟保存一次
    setInterval(this.saveDraft, 2 * 60 * 1000);
  }

  get boundaryRect() {
    return this.overlayMap[BOUNDARY];
  }

  get selectedOverlayTarget() {
    const { type, info } = this.selectedOverlay;
    if (type === "") return null;
    if (type === BOUNDARY) return this.boundaryRect;
    return this.overlayMap[info.id];
  }

  updateBoundaryBackground(url) {
    if (url === "") return;
    this.boundary.background = url;
    this.updateBoundaryBackgroundImageLayer(url);
  }

  updateBoundaryBackgroundImageLayer(url) {
    if (this.boundaryRect == null) return;
    const bounds = this.boundaryRect.getBounds();
    if (this.boundaryImageLayer == null) {
      this.boundaryImageLayer = new AMap.ImageLayer({
        url,
        bounds,
        zIndex: BOUNDARY_IMAGE_Z_INDEX,
        opacity: 0.5,
      });
      this.map.addLayer(this.boundaryImageLayer);
    } else {
      this.boundaryImageLayer.setImageUrl(url);
    }
  }

  showSearch() {
    this.status.searching = !this.status.searching;
  }

  async setMap(map) {
    this.map = map;
    this.polygonEditor = new AMap.PolygonEditor(map);
    // this.polygonEditor.
    this.rectEditor = new AMap.RectangleEditor(map);
    // 
    this.polylineEditor = new AMap.PolylineEditor(map, undefined, {
      createOptions: {
        cursor: "pointer",
        zIndex: OVERLAY_Z_INDEX,
      },
      editOptions: {
        zIndex: OVERLAY_Z_INDEX
      }
    });

    const projectOptions = await getItem(CACHE_KEY);
    if (projectOptions != null) {
      this.openProject(JSON.parse(projectOptions));
    }

  }

  newPolygon() {
    if (this._updateEditing()) {
      this.setStatus(STATUS_NEW_POLYLON);
      this.polygonEditor.setTarget(null);
      this.polygonEditor.open();
      // this.polygonEditor.getTarget().setzIndex(OVERLAY_Z_INDEX);
    } else {
      this.finishEditOverlay();
    }
  }

  addPolygon(polygon) {
    if (polygon == null) return;
    const id = this.getOverlayId(polygon);
    polygon.setOptions({
      strokeColor: POLYLON_STROKE,
      strokeOpacity: 1,
      strokeWeight: 2,
      strokeStyle: "solid",
      cursor: "pointer",
      draggable: false,
      bubble: true,
    })
    this.overlayMap[id] = polygon;
    this.polygons.push({
      id,
      name: "",
      category: "building",
      height: 10,
    });
    polygon.on("click", this.onPolygonClick);
  }

  newPolyline() {
    if (this._updateEditing()) {
      this.setStatus(STATUS_NEW_POLYLINE);
      this.polylineEditor.setTarget(null);
      this.polylineEditor.open();
    } else {
      this.finishEditOverlay();
    }
  }

  newMarker() {
    const { lng, lat } = this.map.getCenter();
    const marker = new AMap.Marker({
      position: [lng, lat],
      anchor: 'bottom-center'
    });
    this.map.add(marker);
    this.addMarker(marker);
  }

  addMarker(marker) {
    const id = this.getOverlayId(marker);
    this.overlayMap[id] = marker;
    this.markers.push({
      id
    });
    marker.setOptions({
      bubble: true,
    })
    marker.on("click", this.onMarkerClick);
    marker.on("mouseup", this.updateMarkerPosition)
  }
  
  addPolyline(polyline) {
    if (polyline == null) return;
    const id = this.getOverlayId(polyline);
    polyline.setOptions({
      strokeWeight: 10,
      strokeColor: POLYLINE_STROKE,
      strokeOpacity: 1,
      strokeStyle: "solid",
      cursor: "pointer",
      zIndex: 50,
      bubble: true
    })
    this.overlayMap[id] = polyline;
    this.polylines.push({
      id,
      name: "",
      width: 5
    });
    polyline.on("click", this.onPolylineClick);
  }

  getOverlayId(overlay) {
    return overlay._amap_id;
  }

  editBoundary() {
    if (this.boundaryRect == null) {
      const bounds = getRectBounds(this.map);
      this.addBoundary(bounds);
    }
    if (this._updateEditing()) {
      this.rectEditor.setTarget(this.boundaryRect);
      this.setStatus(STATUS_EDIT_BOUNDARY);
      this.rectEditor.open();
    } else {
      this.finishEditOverlay();
    }
  }

  addBoundary(bounds) {
    const rect = new AMap.Rectangle({
      bounds,
      fillOpacity: 0.1,
      bubble: true,
      cursor: "pointer",
      zIndex: BOUNDARY_Z_INDEX,
      strokeColor: BOUNDARY_STROKE,
    });
    this.overlayMap[BOUNDARY] = rect;
    this.map.add(rect);
    rect.on("click", this.onBoundaryClick);
  }

  setStatus(type = "") {
    this.status.type = type;
    return this;
  }

  _updateEditing() {
    this.status.editing = !this.status.editing;
    return this.status.editing;
  }

  onOverlayClick(event, type) {
    const { selectedOverlay } = this;
    const { target } = event;

    // 编辑状态下不能选中
    // if (this.status.editing) return;

    if (this.selectedOverlayTarget) {
      this.highlightOverlay(this.selectedOverlayTarget, selectedOverlay.type, false);
    }

    selectedOverlay.type = type;

    const id = this.getOverlayId(target);
    
    if (type === POLYGON) {
      const polygon = this.polygons.find((polygon) => polygon.id == id);
      selectedOverlay.info = polygon;
    } else if (type === POLYLINE) {
      const polyline = this.polylines.find((polyline) => polyline.id == id);
      selectedOverlay.info = polyline;
    } else if (type === MARKER) {
      let { lng, lat } = target.getPosition();
      [lng, lat] = convertCoord([lng, lat]);
      selectedOverlay.info = {
        id,
        lng,
        lat
      };
    } else {
      selectedOverlay.info = this.boundary;
    }

    this.highlightOverlay(this.selectedOverlayTarget, selectedOverlay.type, true);
  }

  updateMarkerPosition() {
    const { info , type } = this.selectedOverlay;
    if (type !== MARKER) return;
    let { lng, lat } = this.selectedOverlayTarget.getPosition();
    [lng, lat] = convertCoord([lng, lat]);
    info.lng = lng;
    info.lat = lat;
  }

  // FIXME: 折线较短的时候不容易选中...
  switchLine() {
    if (this._lineIndex == null) {
        this._lineIndex = 0;
    }
    if (this._lineIndex >= this.polylines.length) {
      this._lineIndex = 0;
    }
    const { selectedOverlay } = this;
    if (this.selectedOverlayTarget) {
      this.highlightOverlay(this.selectedOverlayTarget, selectedOverlay.type, false);
    }
    selectedOverlay.type = "polyline";
    const id = this.polylines[this._lineIndex].id;
    const polyline = this.polylines.find((polyline) => polyline.id == id);
    selectedOverlay.info = polyline;
    this.highlightOverlay(this.selectedOverlayTarget, selectedOverlay.type, true);
    this._lineIndex++;
  }

  editOverlay() {
    const {selectedOverlay} = this;
    if (selectedOverlay.type == "") return;
    if (this.status.editing) {
      this.finishEditOverlay();
      return;
    }
    const {type} = selectedOverlay;
    const target = this.selectedOverlayTarget;
    switch (type) {
      case BOUNDARY:
        this.rectEditor.setTarget(target);
        this.rectEditor.open();
        this.setStatus(STATUS_EDIT_BOUNDARY);
        break;
      case POLYGON:
        this.polygonEditor.setTarget(target);
        this.polygonEditor.open();
        this.setStatus(STATUS_EDIT_POLYLON);
        break;
      case POLYLINE:
        this.polylineEditor.setTarget(target);
        this.polylineEditor.open();
        this.setStatus(STATUS_EDIT_POLYLINE);
        break;
    }
    this.status.editing = true;
  }

  deleteOverlay() {
    const { type } = this.selectedOverlay;
    if ( type == "") return;
    const target = this.selectedOverlayTarget;
    const id = this.getOverlayId(target);
    
    switch (type) {
      case POLYGON:
        const polyon = this.polygons.find(polygon => polygon.id == id);
        this.polygons.splice(this.polygons.indexOf(polyon), 1);
        break;
      case POLYLINE:
        const polyline = this.polylines.find(polyline => polyline.id == id);
        this.polylines.splice(this.polylines.indexOf(polyline), 1);
        break;
      case BOUNDARY:
        delete this.overlayMap[BOUNDARY];
        if (this.boundaryImageLayer != null) {
          this.map.remove(this.boundaryImageLayer);
          this.boundaryImageLayer = null;
        }
        this.boundary.background = "";
        break;
    }
    this.selectedOverlay.info = null;
    this.selectedOverlay.type = "";
    this.map.remove(target);
    delete this.overlayMap[id];
  }

  highlightOverlay(overlay, type, highlight) {
    const isPolygon = type === POLYGON; 
    const isPolyline = type === POLYLINE;
    const isMarker = type === MARKER;
    const strokeColor = isPolygon ? POLYLON_STROKE : isPolyline ? POLYLON_STROKE : BOUNDARY_STROKE;

    const options = {
      strokeColor: highlight ? SELECTED_STROKE : strokeColor
    };
    if (isPolygon || isPolyline || isMarker) {
      options.draggable = highlight;
      options.cursor = highlight ? "move" : "pointer";
    }
    if (isMarker && highlight) {
      this.selectedOverlayTarget.on("moveend", this.updateMarkerPosition);
    }

    overlay?.setOptions(options)
  }

  copyOverlay() {
    const { type } = this.selectedOverlay;
    if (type !== POLYGON) {
      ElMessage({
        message: "暂不支持复制多边形之外的图层",
        type: "warning",
      });
      return;
    }
    if (this.status.editing) {
      this.finishEditOverlay();
    }

    // this.highlightOverlay(this.selectedOverlayTarget, type, false);

    const coordinates = getOverlayCoordinates(this.selectedOverlayTarget);
    const path = coordinates.map((lngLat) => new AMap.LngLat(lngLat[0], lngLat[1]));
    const polygonOverlay = new AMap.Polygon({
      path
    });

    this.map.add(polygonOverlay);
    this.addPolygon(polygonOverlay);

    this.onOverlayClick({
      target: polygonOverlay
    }, type);
    ElMessage({
      message: "复制成功!",
      type: "success",
    });
  }

  exportGeoJSON() {
    const features = this.polygons.map((polygon) => {
      const { id, name, height, category } = polygon;
      const coordinates = getOverlayCoordinates(this.overlayMap[id]);
      const properties = { name, type: "multiPolygon" };
      if (category === BUILDING) {
        properties.building = true;
        properties.height = height;
      } else if (category === WATER) {
        properties.water = true;
      } else {
        properties.grass = true;
      }

      if (turf.booleanClockwise(coordinates)) {
        coordinates.reverse();
      }

      return {
        type: "Feature",
        properties,
        geometry: {
          type: "Polygon",
          coordinates: [coordinates]
        }
      }
    });

    features.push(...this.polylines.map(polyline => {
      const { name, id, width } = polyline;
      const coordinates = getOverlayCoordinates(this.overlayMap[id], false);
      return {
        type: "Feature",
        properties: {
          road: true,
          name,
          width
        },
        geometry: {
          type: "LineString",
          coordinates: coordinates
        }
      }
    }));

    // only bbox.
    if (features.length === 0 && this.boundaryRect != null) {
      const [west, south, east, north] = getBBox(this.boundaryRect.getBounds());
      features.push({
        type: "Feature",
        properties: { "calder:bbox": true },
        geometry: {
          type: "Polygon",
          coordinates: [
            [
              [west, south],
              [west, north],
              [east, north],
              [east, south],
              [west, south],
            ]
          ]
        }
      })
    }

    convertFeatures(features);

    downloadGeoJSON({
      type: "FeatureCollection",
      features
    });
  }

  importGeoJSON() {
    
  }

  finishEditOverlay() {
    const {type} = this.status;
    if (type === "") return;
    this.status.editing = false;

    switch (type) {
      case STATUS_NEW_POLYLINE:
      case STATUS_EDIT_POLYLINE:
        this.polylineEditor.close();
        (type === STATUS_NEW_POLYLINE) && this.addPolyline(this.polylineEditor.getTarget());
        this.polylineEditor.setTarget(null);
        break;
      case STATUS_NEW_POLYLON:
      case STATUS_EDIT_POLYLON:
        this.polygonEditor.close();
        (type === STATUS_NEW_POLYLON) && this.addPolygon(this.polygonEditor.getTarget());
        this.polygonEditor.setTarget(null);
        break;
      case STATUS_EDIT_BOUNDARY:
        this.rectEditor.setTarget(null);
        this.rectEditor.close();
        this.boundaryImageLayer?.setBounds(this.boundaryRect.getBounds());
        break;
    }

    this.setStatus();
  }

  openProject(options) {
    const { boundary, polylines, polygons, markers = [], center = CENTER, zoom = ZOOM } = options;
    
    editor.map.setCenter(new AMap.LngLat(center[0], center[1]));
    editor.map.setZoom(zoom);

    if (boundary != null) {
      const { image, bbox } = boundary;
      const [west, south, east, north] = bbox;
      const bounds = new AMap.Bounds(
        new AMap.LngLat(west, south),
        new AMap.LngLat(east, north)
      );
      this.addBoundary(bounds);
      this.updateBoundaryBackground(image);
    }

    this.polygons = polygons.map((polygon) => {
      const {name, height, category, coordinates} = polygon;

      const path = coordinates.map((lngLat) => new AMap.LngLat(lngLat[0], lngLat[1]));

      const polygonOverlay = new AMap.Polygon({
        path
      });

      this.map.add(polygonOverlay);
      this.addPolygon(polygonOverlay);

      return {
        id: this.getOverlayId(polygonOverlay),
        name,
        height,
        category
      }
    });

    this.markers = markers.map(([lng, lat]) => {
      const marker = new AMap.Marker({
        position: [lng, lat],
        anchor: 'bottom-center'
      });
      this.map.add(marker);
      this.addMarker(marker);
      return {
        id: this.getOverlayId(marker)
      };
    })

    this.polylines = polylines.map((line) => {
      const {name, coordinates, width} = line;

      const polyline = new AMap.Polyline({
        path: coordinates.map((lngLat) => new AMap.LngLat(lngLat[0], lngLat[1]))
      });

      this.map.add(polyline);
      this.addPolyline(polyline);

      return {
        id: this.getOverlayId(polyline),
        name,
        width
      }
    })
  }

  saveProject() {
    const projectOptions = this._getProjectOptions();

    downloadGeoJSON(projectOptions, "fineGeoJSON.json");
  }

  _getProjectOptions() {
    const {lng, lat} = editor.map.getCenter();
    const projectOptions = {
      center: [lng, lat],
      zoom: editor.map.getZoom(),
    };

    projectOptions.polygons = this.polygons.map((polygon) => {
      const { id, name, height, category } = polygon;
      const coordinates = getOverlayCoordinates(this.overlayMap[id]);
      return {
        name,
        height,
        category,
        coordinates,
      }
    });

    projectOptions.polylines = this.polylines.map((polyline) => {
      const { id, name, width } = polyline;
      const coordinates = getOverlayCoordinates(this.overlayMap[id], false);
      return {
        name,
        width,
        coordinates,
      }
    });

    projectOptions.markers = this.markers.map(({id}) => {
      const { lng, lat } = this.overlayMap[id].getPosition();
      return [lng, lat];
    });

    if (this.boundaryRect != null) {
      projectOptions.boundary = {
        bbox: getBBox(this.boundaryRect.getBounds()),
        image: this.boundary.background
      }
    }
    return projectOptions;
  }

  setupHotKeys() {
    registerHotKey(49, this.newPolyline.bind(this), { altKey: true} );
    registerHotKey(50, this.newPolygon.bind(this), { altKey: true} );
    registerHotKey(83, this.showSearch.bind(this), { altKey: true} );
    registerHotKey(83, this.saveDraft, { ctrlKey: true} );
    registerHotKey(32, this.finishEditOverlay.bind(this));
    registerHotKey(69, this.editOverlay.bind(this), { altKey: true});
    registerHotKey(68, this.deleteOverlay.bind(this), { altKey: true});
    // FieME: 折线的选中有bug, 先增加一个切换选中线的操作
    registerHotKey(76, this.switchLine.bind(this), { altKey: true});
    // 复制图层
    registerHotKey(67, this.copyOverlay.bind(this), { altKey: true});
  }

  async _saveDraft() {
    if (this.status.editing) return; // 正在编辑不保存
    const projectOptions = this._getProjectOptions();

    await setItem(CACHE_KEY, JSON.stringify(projectOptions));

    ElMessage({
      message: "草稿保存成功",
      type: "success",
    });
  }

}

export const editor = new Editor();
window.editor = editor;

function downloadGeoJSON(json, name="calder.geojson") {
  const content = JSON.stringify(json);
  const blob = new Blob([content], { type: "text/json" });
  const a = document.createElement("a");
  a.href = URL.createObjectURL(blob);
  a.download = name;
  a.click();
}

function getRectBounds(map) {
  const { northEast, southWest } = map.getBounds();
  let { lng: east, lat: north } = northEast;
  let { lng: west, lat: south } = southWest;
  const { lng, lat } = map.getCenter();
  const dLng = (east - west) * 0.4,
    dLat = (north - south) * 0.4;
  east = lng + dLng;
  west = lng - dLng;
  north = lat + dLat;
  south = lat - dLat;

  return new AMap.Bounds(
    new AMap.LngLat(west, south),
    new AMap.LngLat(east, north)
  );
}

function getBBox(bounds) {
  const { northEast, southWest } = bounds;
  let { lng: east, lat: north } = northEast;
  let { lng: west, lat: south } = southWest;
  return [west, south, east , north];
}

function getOverlayCoordinates(overlay, polygon = true) {
  let paths = overlay.getPath();
  if (paths.length === 1) {
    paths = paths[0];
  }
  const lngLats = paths.map((path) => {
    const { lng, lat } = path;
    return [lng, lat];
  });
  if (polygon) {
    const [fLng, fLat] = lngLats[0];
    const [lLng, lLat] = lngLats[lngLats.length - 1];
    if (fLng !== lLng || fLat !== lLat) {
      lngLats.push([...lngLats[0]])
    }
  }

  return lngLats;
}

function convertCoord(coord) {
  const {lng, lat} = gcj02_To_gps84(coord[0], coord[1]);
  coord[0] = lng;
  coord[1] = lat;
  return coord;
}

function convertFeatures(features) {
  features.map(feature => {
    const { properties, geometry} = feature;
    const { type, coordinates} = geometry;

    if (type === "LineString") {
      coordinates.map(convertCoord);
    } else if (type === "Polygon") {
      coordinates[0].map(convertCoord);
    }
  })
}